package sk.ygor.raynor.rebind.serializable.generic


import com.google.gwt.core.ext.typeinfo._
import com.google.gwt.core.ext._
import com.google.gwt.json.client.JSONObject
import com.google.gwt.user.rebind.{SourceWriter, ClassSourceFileComposerFactory}
import sk.ygor.raynor.rebind.serializable.{JSONSerializableAnalysis, GenericTypeStructure}
import sk.ygor.xtemplate.XTemplate
import sk.ygor.raynor.client.json.{JSONSerializationException, JSONSerializer}
import sk.ygor.raynor.rebind.common.GeneratorUtils

object JSONSerializerGenericTypeGenerator extends AnyRef with GeneratorUtils {
  var context: GeneratorContext = null
  var logger: TreeLogger = null
  var structure: GenericTypeStructure = null


  // inherited generator method
  def generate(logger: TreeLogger, context: GeneratorContext, structure: GenericTypeStructure) {
    this.context = context
    this.logger = logger
    this.structure = structure
    generateClass
  }

  def generateClass {
    // get print writer that receives the source code
    val printWriter = context.tryCreate(logger, structure.serializer.packageName, structure.serializer.simpleName)
    // print writer if null, source code has ALREADY been generated, return
    if (printWriter == null) {
      return
    }

    val typeOracle = context.getTypeOracle()
    val composer = new ClassSourceFileComposerFactory(
      structure.serializer.packageName, structure.serializer.simpleName
      )

    val interfaceType = typeOracle.getParameterizedType(
      typeOracle.getType(classOf[JSONSerializer[_]].getName()).isGenericType(),
      Array[JClassType](structure.asClassType)
      )

    composer.addImplementedInterface(interfaceType.getParameterizedQualifiedSourceName())
    composer.addImplementedInterface(classOf[JSONSerializer.HasExternalName].getName.replaceAll("\\$", "\\."))
    composer.addImplementedInterface(classOf[JSONSerializer.Configuration].getName.replaceAll("\\$", "\\."))

    composer addImport (classOf[JSONObject].getPackage().getName() + ".*")
    composer addImport (classOf[JSONSerializationException].getPackage().getName() + ".*")

    val sourceWriter = composer.createSourceWriter(context, printWriter)
    generateBody(sourceWriter)

    sourceWriter.outdent()
    sourceWriter.println("}")

    context.commit(logger, printWriter)
  }


  def generateBody(sw: SourceWriter) {
    val xtpl = new XTemplate("sk/ygor/raynor/rebind/serializable/generic/JSONSerializerGenericType.xtpl")
    xtpl.assign("serializerClassName", structure.serializer.fullName)
    xtpl.assign("javaObjectClassFullName", structure.fullName)
    xtpl.assign("javaObjectClassBlankName", structure.blankName)
    xtpl.assign("parametrizedClassName", parametrizedClassNameSourceCode(structure.parametrizedClassName))
    structure.properties match {
      case Left(reason) => {
        xtpl.assign("reason", reason + ". Class: " + structure.asClassType.toString)
        xtpl.parse("notReady")
      }
      case Right(structureMap) => {
        for (((property, jsonPropertyType), index) <- structureMap.elements.zipWithIndex) {
          xtpl.assign("subSerializerClassName", JSONSerializableAnalysis.getStructureInfo(jsonPropertyType).serializer.fullName)
          xtpl.assign("subSerializerInstance", "subSerializerInstance" + index)
          xtpl.parse("subSerializerInstance")
          xtpl.parse("subSerializerInstanceDelegateTransformer")
          xtpl.assign("jsonPropertyName", property.jsonFieldName)
          xtpl.assign("jsonPropertyNameCamelCase", camelCase(property.jsonFieldName))
          xtpl.parse("isReady.serializeProperty")
          xtpl.parse("isReady.deserializeProperty")
          xtpl.assign("javaObjectFieldType", jsonPropertyType.getParameterizedQualifiedSourceName())
          xtpl.assign("javaObjectFieldName", property.javaFieldName)
          xtpl.parse("isReady.getFromField")
          xtpl.parse("isReady.setToField")
        }
      }
      xtpl.parse("isReady")
    }
    for ((subType, index) <- structure.subTypes.zipWithIndex) {
      xtpl.assign("subType", subType.getQualifiedSourceName())
      xtpl.assign("subTypeSerializerInstance", "subTypeSerializerInstance" + index)
      xtpl.assign("subTypeSerializerClassName", JSONSerializableAnalysis.getStructureInfo(subType).serializer.fullName)
      xtpl.parse("subTypeSerializerInstance")
      xtpl.parse("subTypeSerializerInstanceDelegateTransformer")
      xtpl.parse("subTypeSerialize")
      xtpl.parse("subTypeDeserialize")
    }
    xtpl.parse()
    sw.println(xtpl.getText())
  }


  def camelCase(name: String) = name.substring(0, 1).toUpperCase() + name.substring(1)

}